const fs  = require("fs");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const babel = require("@babel/core");
const path = require("path")

let ID = 0;

function createAsset(filepath){
    // 第1步，读取入口文件内容
    const content = fs.readFileSync(filepath, "utf-8");

    // 第2步，生成AST抽象语法树
    const ast = parser.parse(content, {
        sourceType: "module" // 指定引入的文件为module类型
    })

    // 第3步，利用AST语法树生成当前文件的相关文件依赖
    const dependencies = [];
    traverse(ast, {
        ImportDeclaration: ({node})=>{
            dependencies.push(node.source.value);
        }
    })

    // 第4步，解析AST生成ES5代码
    const { code } = babel.transformFromAstSync(ast, null, {
        presets: ["@babel/preset-env"]
    })
    
    let id = ID++; // 作为该解析文件的标识符

    return {
        id,
        filepath,
        code,
        dependencies
    }
}

// 第6步，创建文件依赖关系图-循环分析所有文件
function createGraph(entry){
    const mainAsset = createAsset(entry);
    let queue = [mainAsset];

    for(const asset of queue){

        const dirname = path.dirname(asset.filepath);
        
        asset.mapping = {};
        
        asset.dependencies.forEach(relativePath=>{
            const absolutePath = path.join(dirname, relativePath); // 获取文件的绝对路径
            const child = createAsset(absolutePath); // 解析依赖文件
            asset.mapping[relativePath] = child.id; // 形成相对路径和模块id的映射关系

            queue.push(child);
        })
    }
    return queue;
}

// 第7步，实现bundle，输出代码
function bundle(graph){
    let modules = "";
    graph.forEach(mod=>{
        modules += `
            ${mod.id}:[
                function(require, module, exports){
                    ${mod.code}
                },
                ${JSON.stringify(mod.mapping)}
            ],
        `
    })

    // 上面形成的modules将作为参数传给下面的立即执行函数
    // 该函数需要实现 require module exports 三个API
    const result = `
        (function(modules){
            // 实现require API
            function require(id){
                const [fn, mapping] = modules[id];

                const localRequire = relativePath=>{
                    return require(mapping[relativePath])
                }

                const module = {
                    exports: {}
                }

                fn(localRequire, module, module.exports);

                return module.exports
            }

            // 打包后的文件，只需要执行入口文件就行
            require(0)
        })({${modules}})
    `
    return result;
}

const graph = createGraph("./src/index.js")
const bundleContent = bundle(graph);

fs.writeFileSync('bundle.js', bundleContent);